package aa;

import java.nio.channels.SocketChannel;
import java.nio.channels.ServerSocketChannel;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;

public class streambase {
	SocketChannel outSo;
	double staTime = 0;
	Map<Short, sendPackMapData> sendPackMap = new HashMap<Short, sendPackMapData>();
	Map<Short, byte[]> recPackMap = new HashMap<Short, byte[]>();

	short sendSelfPos = 0, sendPeerPos = 0, recPos = 0;
	double sendStatusRate;
	Map<Short, Integer> notRecInfo = new HashMap<Short, Integer>();
	Map<Short, Integer> peerNotRecInfo = new HashMap<Short, Integer>();
	double statusGapTime = Double.MAX_VALUE;
	short pushAhead;

	double newPeerTime = -Double.MAX_VALUE;
	double peerMyTime = -Double.MAX_VALUE;
	
	boolean hasStart = false;
	double maxSend = 1;
	double slope = 1;
	int packLimit;
	double updatedTime = helpFunc.getTime();
	int calStaNum = 0;
	double waitingTime = 0;

	float rRaw, wRaw, rNet, wNet;

	float totalRec, blankRec, totalSend, blankSend, statusSend, statusRev;
	boolean isServer;
	Map<String, packStaData> maxSendL = new HashMap<String, packStaData>();
	Map<String, packStaData> maxRec = new HashMap<String, packStaData>();
	Map<String, packStaData> peerMaxSend = new HashMap<String, packStaData>();
	Map<String, packStaData> peerMaxRec = new HashMap<String, packStaData>();
	
	byte[] readBuffer = new byte[0];
	byte[] writeBuffer = new byte[0];
	ServerSocketChannel listSo;
	int listenPort;

	streambase( int pushAhead_arg, int packLimit_arg, boolean isServer_arg) throws Exception {
		sendStatusRate = helpFunc.con_minRate;
		packLimit = packLimit_arg;
		pushAhead =(short) pushAhead_arg;
		isServer = isServer_arg;
		for (short i : helpFunc.circleRange((short) 0, pushAhead)) {
			notRecInfo.put(i, 0);
		}
		listSo = ServerSocketChannel.open();
		InetSocketAddress add = new InetSocketAddress("localhost", 0);
		listSo.bind(add);
		InetSocketAddress tt = (InetSocketAddress) listSo.getLocalAddress();
		listenPort = tt.getPort();
	}

	public void makeOutSo() throws Exception {
		outSo = listSo.accept();
		listSo.close();
		outSo.configureBlocking(false);
	}

	public void dealStatusBack(byte[] re) {
		updatedTime = helpFunc.getTime();
		structWrapper s = new structWrapper(re);
		s.readByte();
		double peerTime = s.readDouble();
		if (peerTime < newPeerTime)
			return;
		newPeerTime = peerTime;
		double peerMyTime_h = s.readDouble();
		peerMyTime = peerMyTime_h;
		if (isServer) {
			sendStatusRate = s.readDouble();
			slope = s.readDouble();
			maxSend = s.readDouble();
			waitingTime = s.readDouble();
		} else {
			s.readDouble();
			s.readDouble();
			s.readDouble();
			s.readDouble();
		}
		short pms = s.readWord();
		short pmr = s.readWord();
		helpFunc.addPackSta(peerMaxSend, pms, peerMyTime_h + statusGapTime / 2);
		helpFunc.addPackSta(peerMaxRec, pmr, peerMyTime_h + statusGapTime / 2);
		short peerPos = s.readWord();
		peerNotRecInfo.clear();
		sendPeerPos = peerPos;
		byte[] st = s.getLeftData();
		String ss = new String();
		int ra = st.length;
		for (int i = 0; i < ra; i++) {
			byte j = st[i];
			String s2 = Integer.toBinaryString(j);
			String zo = "0";
			ss += helpFunc.getMulString(zo, 8 - s2.length()) + s2;

		}
		int co = 0;
		for (short one : helpFunc.circleRange(peerPos, helpFunc.circleAdd(peerPos, pushAhead))) {
			if (ss.charAt(co) == '1') {
				peerNotRecInfo.put(one, 0);
			}
			co += 1;
		}

	}

	public void dealPushBack(byte[] re) {
		structWrapper s = new structWrapper(re);
		s.readByte();
		short num = s.readWord();
		if (notRecInfo.containsKey(num)) {
			notRecInfo.remove(num);
			recPackMap.put(num, s.getLeftData());
		}
	}
	
	public void deal_data_back(ArrayList<byte[]> l) {
		for (byte[] re : l) {
			byte ty = re[0];
			totalRec += 1;
			if (ty == 0) {
				dealStatusBack(re);
				statusRev += 1;
			} else if (ty == 1) {
				dealPushBack(re);
			} else {
				blankRec += 1;
			}

		}
		if (peerMyTime != -Double.MAX_VALUE) {
			statusGapTime = helpFunc.getTime() - peerMyTime;
			if (!hasStart) {
				hasStart = true;
				helpFunc.print("got conn");
			}
		}
	}
	

	

	public void read() {
		if (!hasStart) {
			return;
		}
		if (!recPackMap.containsKey(recPos))
			return;
		int addL = 0;
		int bufferL = readBuffer.length;
		ArrayList<byte[]> tempL = new ArrayList<byte[]>();
		ArrayList<Short> l = helpFunc.circleRange(recPos, helpFunc.circleAdd(recPos, pushAhead));
		for (short one : l) {
			if (!recPackMap.containsKey(one))
				break;
			if (bufferL + addL > helpFunc.con_streamBufferSize)
				break;
			tempL.add(recPackMap.get(one));
			addL += recPackMap.get(one).length;

			recPackMap.remove(one);
			recPos = helpFunc.circleAddOne(one);
			notRecInfo.put(helpFunc.circleAdd(one, pushAhead), 0);
		}
		if (addL > 0) {
			byte[] newB = new byte[bufferL + addL];
			int destPos = 0;
			System.arraycopy(readBuffer, 0, newB, destPos, bufferL);
			destPos += bufferL;

			for (byte[] one : tempL) {
				System.arraycopy(one, 0, newB, destPos, one.length);
				destPos += one.length;
			}
			readBuffer = newB;
			rNet += addL;
		}

	}

	void write() {
		if (!hasStart) {
			return;
		}
		int lB = writeBuffer.length;
		if(lB==0)return;
		int newPos = 0;
		while (true) {
			if (lB == 0)
				break;
			if (helpFunc.circleBig(helpFunc.circleAdd(sendPeerPos, pushAhead), sendSelfPos) == sendSelfPos)
				break;
			byte[] s1;
			if (newPos + packLimit < writeBuffer.length) {
				s1 = new byte[packLimit];
				System.arraycopy(writeBuffer, newPos, s1, 0, packLimit);
			} else {

				s1 = new byte[writeBuffer.length - newPos];
				System.arraycopy(writeBuffer, newPos, s1, 0, writeBuffer.length - newPos);
			}
			newPos += packLimit;
			sendPackMapData d = new sendPackMapData();
			d.con = s1;
			sendPackMap.put(sendSelfPos, d);			
			wNet += s1.length;
			sendSelfPos = helpFunc.circleAddOne(sendSelfPos);
			if (newPos >= lB)
				break;
		}
		if (writeBuffer.length - newPos <= 0) {
			writeBuffer = new byte[0];
			return;
		}

		byte[] newW = new byte[writeBuffer.length - newPos];
		System.arraycopy(writeBuffer, newPos, newW, 0, writeBuffer.length - newPos);

		writeBuffer = newW;
	}
	public ArrayList<byte[]> get_data_to_send(int n) {
		totalSend += n;
		clearRecording();
		int statusNum = 0;
		if (!hasStart)
			statusNum = n;
		else {
			calStaNum += n;
			statusNum = (int) (calStaNum * sendStatusRate);
			if (statusNum > n)
				statusNum = n;
			calStaNum -= statusNum / sendStatusRate;
		}
		byte[] st = getOneStatus();
		ArrayList<byte[]> l = findNPack(n - statusNum);
		statusSend += statusNum;
		ArrayList<byte[]> ret = new ArrayList<byte[]>();
		for (int i = 0; i < statusNum; i++) {
			ret.add(st);
		}
		ret.addAll(l);
		for (int i = 0; i < n - statusNum - l.size(); i++) {
			byte[] re = { 2 };
			ret.add(re);
			blankSend += 1;
		}
		return ret;
	}
	
	byte[] getOneStatus() {
		structWrapper re = new structWrapper(1400);
		re.writeByte((byte) 0);
		re.writeDouble(helpFunc.getTime());
		re.writeDouble(newPeerTime);

		re.writeDouble(sendStatusRate);
		re.writeDouble(slope);
		re.writeDouble(maxSend);
		re.writeDouble(waitingTime);

		re.writeWord(helpFunc.getPackStaBigV(maxSendL));
		re.writeWord(helpFunc.getPackStaBigV(maxRec));
		re.writeWord(recPos);
		int co = 0;
		String ss = new String();
		ArrayList<Short> l = helpFunc.circleRange(recPos, helpFunc.circleAdd(recPos, pushAhead));
		for (short i : l) {
			if (!notRecInfo.containsKey(i))
				ss += "0";
			else {
				ss += "1";
			}
			co += 1;
			if (co == 8) {
				re.writeByte((byte) Integer.parseInt(ss, 2));
				co = 0;
				ss = new String();
			}
		}
		String zo = "0";
		ss += helpFunc.getMulString(zo, 8 - ss.length());
		re.writeByte((byte) Integer.parseInt(ss, 2));
		return re.getWriteData();
	}
	void clearRecording() {
		for (short i : helpFunc.circleRange(sendPeerPos, sendSelfPos)) {
			if (!peerNotRecInfo.containsKey(i))
				continue;
			Map<String, Double> m = sendPackMap.get(i).sendRecording;
			ArrayList<String> ll = new ArrayList<String>(m.keySet());
			for (String k : ll) {
				double v = m.get(k);
				if (v < helpFunc.getTime() - statusGapTime - waitingTime)
					sendPackMap.get(i).sendRecording.remove(k);
			}
		}
	}

	public static void main(String[] args) throws Exception {
		System.currentTimeMillis();

	}
	int findMinTimes(Map<Integer, tempMData> m) {
		double mi = Double.MAX_VALUE;
		int k = -1;
		for (int i : m.keySet()) {
			tempMData j = m.get(i);
			if (j.times < mi && j.realTimes < maxSend) {
				mi = j.times;
				k = i;
			}
		}
		return k;
	}

	ArrayList<byte[]> findNPack(int n) {
		ArrayList<byte[]> ret = new ArrayList<byte[]>();
		if (n == 0)
			return ret;
		Map<Integer, tempMData> m = new HashMap<Integer, tempMData>();
		int co = 0;
		ArrayList<Short> l = helpFunc.circleRange(sendPeerPos, sendSelfPos);
		for (short i : l) {
			if (!peerNotRecInfo.containsKey(i))
				continue;
			tempMData d = new tempMData();
			d.originNum = i;
			d.times = sendPackMap.get(i).sendRecording.size();
			d.realTimes = sendPackMap.get(i).sendRecording.size();
			m.put(co, d);
			co += 1;
		}
		int l2 = m.size();
		if (l2 == 0)
			return ret;
		double perGap = slope / l2;
		for (int k : m.keySet()) {
			m.get(k).times += k * perGap;//?
		}
		while (true) {
			int k = findMinTimes(m);
			if (k == -1)
				return ret;
			m.get(k).times += 1;
			m.get(k).realTimes += 1;
			String u = UUID.randomUUID().toString();
			short ori = m.get(k).originNum;		
			sendPackMap.get(ori).sendRecording.put(u, helpFunc.getTime());
			byte[] re = new byte[3 + sendPackMap.get(ori).con.length];
			re[0] = 1;
			System.arraycopy(ByteBuffer.allocate(2).order(ByteOrder.nativeOrder()).putShort((short) ori).array(), 0, re,
					1, 2);
			System.arraycopy(sendPackMap.get(ori).con, 0, re, 3, sendPackMap.get(ori).con.length);
			ret.add(re);
			n -= 1;
			if (n == 0)
				return ret;
		}
	}

	
	
	

}
